//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   CA Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------

#include "NjExample01.h"
#include "NjAtomicTest.h"
#include "NjTestUnit.h"
#include "NjRandUint.h"
#include "NjExampleRandGenerator.h"
#include "NjSharedSyncObjectConstants.h"

class NjAtomicTest::Static
{
public:
	static void RunPointer(void);

private:
	NJ_DISABLE_INSTANCE(Static);
};
//----------------------------------------------------------------------------
template<typename tType>
class NjAtomicTest::TypeData
{
public:
	static void Run();

	static NjCharA const * const ms_szType;

private:
	typedef NjRandUint<EXAMPLE_RAND_GENERATOR> Rand;
	static tType Random(Rand & kRand);

	NJ_DISABLE_INSTANCE(TypeData);
};
//----------------------------------------------------------------------------
void NjAtomicTest::Run()
{
	TypeData<NjUint8>::Run();
	TypeData<NjUint16>::Run();
	TypeData<NjUint32>::Run();
	TypeData<NjUint64>::Run();
	TypeData<NjInt8>::Run();
	TypeData<NjInt16>::Run();
	TypeData<NjInt32>::Run();
	TypeData<NjInt64>::Run();
#if NJ_TYPES_CHAR_8_IS_BUILT_IN_TYPE
	TypeData<NjChar8>::Run();
#endif // NJ_TYPES_CHAR_8_IS_BUILT_IN_TYPE
#if NJ_TYPES_CHAR_16_IS_BUILT_IN_TYPE
	TypeData<NjChar16>::Run();
#endif // NJ_TYPES_CHAR_16_IS_BUILT_IN_TYPE
#if NJ_TYPES_CHAR_32_IS_BUILT_IN_TYPE
	TypeData<NjChar32>::Run();
#endif // NJ_TYPES_CHAR_32_IS_BUILT_IN_TYPE
	Static::RunPointer();
}
//----------------------------------------------------------------------------
void NjAtomicTest::Static::RunPointer()
{
	typedef NjAtomic<NjUint *> Atomic;

	NJ_TEST_UNITS_GROUP("NjAtomic<NjUint *>");
		NJ_TEST_UNIT("All methods (single threaded)")
			NjUint uValue1;
			NjUint uValue2;
			Atomic kAtomic(&uValue1);

			NJ_TEST(kAtomic.Get()==&uValue1);

			NJ_TEST(kAtomic.PostExchange(&uValue2)==&uValue1);
			NJ_TEST(kAtomic.Get()==&uValue2);

			NJ_TEST(kAtomic.PostCompareExchange(&uValue1, NJ_NULL)==&uValue2);
			NJ_TEST(kAtomic.Get()==&uValue2);
			NJ_TEST(kAtomic.PostCompareExchange(&uValue2, NJ_NULL)==&uValue2);
			NJ_TEST(kAtomic.Get()==NJ_NULL);

			NjUint * pPtr(&uValue1);
			kAtomic.Set(pPtr);

			kAtomic.Increment();
			++pPtr;
			NJ_TEST(kAtomic.Get()==pPtr);
			NJ_TEST(kAtomic.PostIncrement()==pPtr);
			++pPtr;
			NJ_TEST(kAtomic.Get()==pPtr);
			++pPtr;
			NJ_TEST(kAtomic.PreIncrement()==pPtr);
			NJ_TEST(kAtomic.Get()==pPtr);

			kAtomic.Decrement();
			--pPtr;
			NJ_TEST(kAtomic.Get()==pPtr);
			NJ_TEST(kAtomic.PostDecrement()==pPtr);
			--pPtr;
			NJ_TEST(kAtomic.Get()==pPtr);
			--pPtr;
			NJ_TEST(kAtomic.PreDecrement()==pPtr);
			NJ_TEST(kAtomic.Get()==pPtr);

			kAtomic.Add(10);
			pPtr+=10;
			NJ_TEST(kAtomic.Get()==pPtr);
			NJ_TEST(kAtomic.PostAdd(10)==pPtr);
			pPtr+=10;
			NJ_TEST(kAtomic.Get()==pPtr);
			pPtr+=10;
			NJ_TEST(kAtomic.PreAdd(10)==pPtr);
			NJ_TEST(kAtomic.Get()==pPtr);

			kAtomic.Sub(10);
			pPtr-=10;
			NJ_TEST(kAtomic.Get()==pPtr);
			NJ_TEST(kAtomic.PostSub(10)==pPtr);
			pPtr-=10;
			NJ_TEST(kAtomic.Get()==pPtr);
			pPtr-=10;
			NJ_TEST(kAtomic.PreSub(10)==pPtr);
			NJ_TEST(kAtomic.Get()==pPtr);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
}
//----------------------------------------------------------------------------
template<typename tType>
void NjAtomicTest::TypeData<tType>::Run()
{
	typedef TypeData<tType> TypeData;
	typedef NjAtomic<tType> Atomic;

	NJ_TEST_UNITS_GROUP(TypeData::ms_szType);
		NJ_TEST_UNIT("All methods simple (single threaded)")
			Atomic kAtomic(10);

			NJ_TEST(kAtomic.Get()==10);

			NJ_TEST(kAtomic.PreIncrement()==11);
			NJ_TEST(kAtomic.Get()==11);

			NJ_TEST(kAtomic.PostIncrement()==11);
			NJ_TEST(kAtomic.Get()==12);

			kAtomic.Increment();
			NJ_TEST(kAtomic.Get()==13);

			NJ_TEST(kAtomic.PreDecrement()==12);
			NJ_TEST(kAtomic.Get()==12);

			NJ_TEST(kAtomic.PostDecrement()==12);
			NJ_TEST(kAtomic.Get()==11);

			kAtomic.Decrement();
			NJ_TEST(kAtomic.Get()==10);

			NJ_TEST(kAtomic.PreAdd(5)==15);
			NJ_TEST(kAtomic.Get()==15);

			NJ_TEST(kAtomic.PostAdd(22)==15);
			NJ_TEST(kAtomic.Get()==37);

			kAtomic.Add(7);
			NJ_TEST(kAtomic.Get()==44);

			NJ_TEST(kAtomic.PreSub(8)==36);
			NJ_TEST(kAtomic.Get()==36);

			NJ_TEST(kAtomic.PostSub(18)==36);
			NJ_TEST(kAtomic.Get()==18);

			kAtomic.Sub(5);
			NJ_TEST(kAtomic.Get()==13);

			NJ_TEST(kAtomic.PostOr(0x14)==0x0D);
			NJ_TEST(kAtomic.Get()==0x1D);

			NJ_TEST(kAtomic.PostAnd(0x1A)==0x1D);
			NJ_TEST(kAtomic.Get()==0x18);

			NJ_TEST(kAtomic.PostXor(0x3A)==0x18);
			NJ_TEST(kAtomic.Get()==0x22);

			NJ_TEST(kAtomic.PostExchange(0x56)==0x22);
			NJ_TEST(kAtomic.Get()==0x56);

			NJ_TEST(!kAtomic.PostSetBit(0));
			NJ_TEST(kAtomic.Get()==0x57);
			NJ_TEST(kAtomic.PostSetBit(0));
			NJ_TEST(kAtomic.Get()==0x57);
			NJ_TEST(kAtomic.PostResetBit(0));
			NJ_TEST(kAtomic.Get()==0x56);
			NJ_TEST(!kAtomic.PostResetBit(0));
			NJ_TEST(kAtomic.Get()==0x56);

			NJ_TEST(kAtomic.PostCompareExchange(0x54, 0x30)==0x56);
			NJ_TEST(kAtomic.Get()==0x56);
			NJ_TEST(kAtomic.PostCompareExchange(0x56, 0x31)==0x56);
			NJ_TEST(kAtomic.Get()==0x31);
		NJ_TEST_UNIT_END();

		NJ_TEST_UNIT("All methods complex (single threaded)")
			static const NjUint ITERATIONS=1000000*NjSharedSyncObjectConstants::ITERATIONS_FACTOR;
			typename TypeData::Rand kRand;

			enum Operation
			{
				INCREMENT,
				PRE_INCREMENT,
				POST_INCREMENT,
				DECREMENT,
				PRE_DECREMENT,
				POST_DECREMENT,
				ADD,
				PRE_ADD,
				POST_ADD,
				SUB,
				PRE_SUB,
				POST_SUB,
				POST_OR,
				POST_AND,
				POST_XOR,
				POST_EXCHANGE,
				POST_COMPARE_EXCHANGE_SUCCESS,
				POST_COMPARE_EXCHANGE_FAIL,
				POST_SET_BIT,
				POST_RESET_BIT,
				TOT_OPERATIONS
			};

			Atomic kAtomic(0);
			tType uAtomicValue(0);

			NjUint uIteration(ITERATIONS);
			while (uIteration>0)
			{
				--uIteration;

				Operation const eOperation(static_cast<Operation>(kRand()%TOT_OPERATIONS));
				tType uRand(TypeData::Random(kRand));

				switch(eOperation)
				{
					case INCREMENT:
						kAtomic.Increment();
						++uAtomicValue;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case PRE_INCREMENT:
						++uAtomicValue;
						NJ_TEST(kAtomic.PreIncrement()==uAtomicValue);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_INCREMENT:
						NJ_TEST(kAtomic.PostIncrement()==uAtomicValue);
						++uAtomicValue;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case DECREMENT:
						kAtomic.Decrement();
						--uAtomicValue;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case PRE_DECREMENT:
						--uAtomicValue;
						NJ_TEST(kAtomic.PreDecrement()==uAtomicValue);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_DECREMENT:
						NJ_TEST(kAtomic.PostDecrement()==uAtomicValue);
						--uAtomicValue;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case ADD:
						kAtomic.Add(uRand);
						uAtomicValue+=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case PRE_ADD:
						uAtomicValue+=uRand;
						NJ_TEST(kAtomic.PreAdd(uRand)==uAtomicValue);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_ADD:
						NJ_TEST(kAtomic.PostAdd(uRand)==uAtomicValue);
						uAtomicValue+=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case SUB:
						kAtomic.Sub(uRand);
						uAtomicValue-=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case PRE_SUB:
						uAtomicValue-=uRand;
						NJ_TEST(kAtomic.PreSub(uRand)==uAtomicValue);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_SUB:
						NJ_TEST(kAtomic.PostSub(uRand)==uAtomicValue);
						uAtomicValue-=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_OR:
						NJ_TEST(kAtomic.PostOr(uRand)==uAtomicValue);
						uAtomicValue|=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_AND:
						NJ_TEST(kAtomic.PostAnd(uRand)==uAtomicValue);
						uAtomicValue&=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_XOR:
						NJ_TEST(kAtomic.PostXor(uRand)==uAtomicValue);
						uAtomicValue^=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_EXCHANGE:
						NJ_TEST(kAtomic.PostExchange(uRand)==uAtomicValue);
						uAtomicValue=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_COMPARE_EXCHANGE_SUCCESS:
						NJ_TEST(kAtomic.PostCompareExchange(uAtomicValue, uRand)==uAtomicValue);
						uAtomicValue=uRand;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;

					case POST_COMPARE_EXCHANGE_FAIL:
					{
						tType uComparand(TypeData::Random(kRand));
						if (uComparand==uAtomicValue)
							++uComparand;

						NJ_TEST(kAtomic.PostCompareExchange(uComparand, uRand)==uAtomicValue);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;
					}

					case POST_SET_BIT:
					{
						tType const uBit(uRand%(sizeof(tType)*8));
						tType const uFlag(tType(1)<<uBit);

						NJ_TEST(kAtomic.PostSetBit(uBit)==((uAtomicValue&uFlag)!=0));
						uAtomicValue|=uFlag;
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;
					}

					case POST_RESET_BIT:
					{
						tType const uBit(uRand%(sizeof(tType)*8));
						tType const uFlag(tType(1)<<uBit);

						NJ_TEST(kAtomic.PostResetBit(uBit)==((uAtomicValue&uFlag)!=0));
						uAtomicValue&=(~uFlag);
						NJ_TEST(kAtomic.Get()==uAtomicValue);
						break;
					}

					default:
					{
						NJ_TEST_FAIL();
						break;
					}
				}
			}
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
}
//----------------------------------------------------------------------------
template<typename tType>
inline tType NjAtomicTest::TypeData<tType>::Random(Rand & kRand)
{
	return tType(kRand());
}
//----------------------------------------------------------------------------
#if NJ_PLATFORM_32_BIT
	template<>
	inline NjUint64 NjAtomicTest::TypeData<NjUint64>::Random(Rand & kRand)
	{
		return (NjUint64(kRand())<<32)|NjUint64(kRand());
	}
#endif // NJ_PLATFORM_32_BIT
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjUint8>::ms_szType="NjAtomic<NjUint8>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjUint16>::ms_szType="NjAtomic<NjUint16>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjUint32>::ms_szType="NjAtomic<NjUint32>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjUint64>::ms_szType="NjAtomic<NjUint64>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjInt8>::ms_szType="NjAtomic<NjInt8>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjInt16>::ms_szType="NjAtomic<NjInt16>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjInt32>::ms_szType="NjAtomic<NjInt32>";
//----------------------------------------------------------------------------
template<> NjCharA const * const NjAtomicTest::TypeData<NjInt64>::ms_szType="NjAtomic<NjInt64>";
//----------------------------------------------------------------------------
#if NJ_TYPES_CHAR_8_IS_BUILT_IN_TYPE
	template<> NjCharA const * const NjAtomicTest::TypeData<NjChar8>::ms_szType="NjAtomic<NjChar8>";
#endif // NJ_TYPES_CHAR_8_IS_BUILT_IN_TYPE
//----------------------------------------------------------------------------
#if NJ_TYPES_CHAR_16_IS_BUILT_IN_TYPE
	template<> NjCharA const * const NjAtomicTest::TypeData<NjChar16>::ms_szType="NjAtomic<NjChar16>";
#endif // NJ_TYPES_CHAR_16_IS_BUILT_IN_TYPE
//----------------------------------------------------------------------------
#if NJ_TYPES_CHAR_32_IS_BUILT_IN_TYPE
	template<> NjCharA const * const NjAtomicTest::TypeData<NjChar32>::ms_szType="NjAtomic<NjChar32>";
#endif // NJ_TYPES_CHAR_32_IS_BUILT_IN_TYPE
//----------------------------------------------------------------------------
